﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RWLib
{
    public class Tree
    {
        private Node root;
        public static StatementBase actionToRun;
        public static Scenario _scenario;
        public static ActionDomain _domain;
        public static int currentTime;

        public Node Root
        {
            get { return root; }
        }

        public Tree(Node _root) {
            root = _root;
        }

        public static Tree build(Scenario sc, ActionDomain domain, bool initFormula=false) {                        
            Node root = new Node(sc._initialFluents);
            Node parent = root.Clone() ;
            List<Node> parents = new List<Node>() {parent };
            if (initFormula)
            {
                List<List<Fluent>> initStates = sc._initialFormula.Fluents.Distinct().Permutates().Where(l => sc._initialFormula.Eval(l)).ToList();
                parents.Clear();
                foreach (var s in initStates) {
                    parent = new Node(s);
                    parent.Nodes = root.Nodes;
                    parents.Add(parent);
                }
            }
            _scenario = sc;
            _domain = domain;
            int time = 1, lastTime=-1;
            if (initFormula) {
                time = 0;
            }
            for (; sc.ActionsNames.Count()>0; ++time) {
                Tree.currentTime = time;
                List<Node> pparents = parents.Copy();
                parents.Clear();
                foreach (var p in pparents)
                {

                    List<Fluent> currentState = p.Fluents;

                    List<StatementBase> triggeredActions = runTriggers(domain, p.Fluents, time);

                    List<Node> children = new List<Node>();                    

                    Node node = new Node(currentState);
                    node.t = time;
                    children.Add(node);

                    #region scenario
                    if (sc.ActionsNames.ContainsKey(time))
                    {
                        children.Clear();

                        List<string> actionsToRun = sc.ActionsNames[time];                        
                        List<StatementBase> add = new List<StatementBase>();

                        foreach (var n in actionsToRun) {
                            add.AddRange(domain.Actions.GetByName(n));
                        }                        

                        List<List<StatementBase>> resolvedActions=add.ResolveConflicts(currentState);
                        foreach (var bucket in resolvedActions) {
                            var rs = bucket.Where(a => a is ReleaseStatement).GroupBy(a => (a as ReleaseStatement).Effects.Name).Select(g=>g.First()).ToList();
                            var b1 = bucket.Where(a => !(a is ReleaseStatement)).ToList();
                            b1.AddRange(rs);
                            node = new Node(currentState);
                            node.t = time;                            
                            children.Add(node);
                            List<Node> releasesRes = new List<Node>();
                            foreach (var a in b1)
                            {
                                children.ForEach(c =>
                                {
                                    if (!c.actions.Any(aa => aa.Conflict(a, currentState))) {                                
                                    currentState.ForEach(cc => cc.Changed = false);
                                    List<List<Fluent>> states = a.Run(currentState);
                                    if (states.Count > 0) {
                                    if (a.Performed)
                                    {                                        
                                        var changed = states[0].Where(f=>f.Changed).ToList();
                                        changed.ForEach(f =>
                                        {
                                            c.Fluents.Get(f.Name).Value = f.Value;
                                        });

                                        states.Skip(1).ToList().ForEach(cc =>
                                        {                                            
                                            Node child = new Node(c.Fluents);
                                            changed.ForEach(f =>
                                            {
                                                child.Fluents.Get(f.Name).Value = cc.Get(f.Name).Value;
                                            });
                                            child.t = time;
                                            releasesRes.Add(child);
                                        });
                                    }
                                    }
                                    }

                                });

                                children.AddRange(releasesRes.Copy());
                                releasesRes.Clear();
                            }


                            foreach (var aa in b1)
                            {

                                if (aa.Performed)
                                {
                                    children.ForEach(ch =>
                                    {
                                        if (!ch.actions.Any(cc => cc.Conflict(aa, ch.Fluents)) && !ch.actions.Contains(aa))
                                        {
                                            ch.actions.Add(aa);
                                        }
                                    });
                                }
                            }

                            foreach (var pa in sc.ActionsNames[time]) {
                                if (!b1.Select(a=>a.Name).ToList().Contains(pa))
                                {
                                    var aa = domain.Actions.Find(a => a.Name.Equals(pa));
                                    children.ForEach(ch =>
                                    {
                                        if (!ch.actions.Select(a => a.Name).Contains(aa.Name))
                                        {
                                            ch.notDone.Add(aa);
                                        }
                                    });
                                }
                            }

                            foreach (var ta in triggeredActions)
                            {
                                children.ForEach(ch =>
                                {
                                    if (!ch.actions.Contains(ta))
                                    {
                                        //ch.actions.Add(ta);
                                    }
                                });
                            }

                        }
                    }
                    #endregion

                    p.Nodes.AddRange(children);
                    parents.AddRange(children);
                    children.ForEach(c =>
                    {
                        c.actions = c.actions.GroupBy(a => a.Name).Select(g => g.First()).ToList();
                    });
                }
                sc.ActionsNames.Remove(time);
                if (time >= sc.maxTime && lastTime==-1)
                {
                    if (sc.ActionsNames.Count() > 0)
                    {
                        lastTime = sc.ActionsNames.Keys.Min();
                    }
                }
                if (time >= lastTime && lastTime != -1) {
                    break;
                }
            }
            return new Tree(root);
        }

        private static List<StatementBase> runTriggers(ActionDomain domain, List<Fluent> state, int time)
        {
            List<StatementBase> triggers = new List<StatementBase>();
            foreach (var t in domain.Triggers)
            {
                t.StartTime = time;                
                //if (t.Run(state)!=null)
                //{
                    t.Run(state);
                    triggers.AddRange(domain.Actions.GetByName(t.Action.Name).Where(a=>a.StartTime==time).ToList());
                //}
            }
            return triggers;
        }

        private static void addActionToScenario(Scenario sc)
        {            

            if (actionToRun != null)
            {
                if (sc.ActionsNames.ContainsKey(actionToRun.StartTime))
                {
                    sc.ActionsNames[actionToRun.StartTime].Add(actionToRun.Name);
                }
                else
                {
                    sc.ActionsNames.Add(actionToRun.StartTime, new List<string>() { actionToRun.Name });
                }
                actionToRun = null;
            }
        }

        public List<Node> search(Fluent f) {
            return null;
        }

        public override string ToString()
        {
            var sb = new StringBuilder();
            Node n;
            Stack<Node> nodes = new Stack<Node>(root.Nodes);
            while (nodes.Count!=0) {

                n = nodes.Pop();

                sb.Append(n.ToString());

                n.Nodes.ForEach(nn =>
                {
                    nodes.Push(nn);
                });
            }
            return sb.ToString();
        }
    }

    class DistinctReleaseActionComparer : IEqualityComparer<StatementBase>
    {
        public bool Equals(StatementBase x, StatementBase y)
        {
            if (!(x is ReleaseStatement) || !(y is ReleaseStatement)) {
                return false;
            }
            return (x as ReleaseStatement).Effects.Name.Equals((y as ReleaseStatement).Effects.Name);
        }

        public int GetHashCode(StatementBase obj)
        {
            if (!(obj is ReleaseStatement)) {
                return 0;
            }
            return (obj as ReleaseStatement).Name.GetHashCode();
        }
    }    
}